require 'ruby_parser'
require 'pp'
require 'sexp_processor'
require 'yaml'
require 'set'
require_relative 'database'

Feature = Struct.new :fulcrum, :tokens, :unit_test_file
Fulcrum = Struct.new :assertion, :sentiment
Token = Struct.new :id, :importance, :abspos, :type, :value

Unit_test_filename = "twikit_file_reader_test.rb"
unit_test_file = File.open(Unit_test_filename);
unit_test_str = unit_test_file.read

class AstProcessor < SexpProcessor
  
  attr_reader :vars
  attr_reader :features
  
  def initialize
    super
    self.strict = false
    @require_empty = false
    @vars = Hash.new
    @features = Array.new
  end
  
  def process_call(exp)
    type = exp.shift
    obj = exp.shift
    name = exp.shift
    arglist = exp.shift
    
    if(name =~ /assert/ )
      feature = Feature.new
      feature.unit_test_file = Unit_test_filename
      
      fulcrum = Fulcrum.new
      fulcrum.assertion = name
      fulcrum.sentiment = 'positive'
      fulcrum.sentiment = 'negative' if(name =~ /(null|fails|notEquals)/i)
      @vars[feature] = Set.new
      
      tokens = parse_arglist(arglist, feature)
      tokens.flatten!
      
      feature.fulcrum = fulcrum
      feature.tokens = tokens
      
      @features << feature
    end
    
    return s(type, obj, name, exp.arglist)
  end
  
  def parse_arglist(arglist, feature)
    tokens = Array.new
    type = arglist.shift
    
    while arglist.length > 0
      arg = arglist.shift
      argtype = arg.shift
      if(argtype == :lit || argtype == :str)
        argvalue = arg.shift
        token = Token.new
        token.importance = 40
        token.type = 'literal'
        token.value = argvalue
        tokens << token
      elsif(argtype == :lvar || argtype == :ivar)
        @vars[feature] << arg.shift
      elsif(argtype == :call)
        tokens << parse_arglist_call(arg, feature)
      end
    end
    
    return tokens
  end
  
  def parse_arglist_call(exp, feature)
    tokens = Array.new
    obj = exp.shift
    name = exp.shift
    arglist = exp.shift
    
    unless(obj == nil)
      objtype = obj.shift
      if(objtype == :const)
        obj_token = Token.new
        obj_token.importance = 90
        obj_token.type = 'object'
        obj_token.value = obj.shift
        tokens << obj_token
      elsif(objtype == :call)
        tokens << parse_arglist_call(obj, feature)
      elsif(objtype == :lvar || objtype == :ivar)
        @vars[feature] << obj.shift
      end
    end
    
    tokens << parse_arglist(arglist, feature)
    return tokens
  end
  
end

class TokenFinder < SexpProcessor
  
  attr_reader :tokens
  
  def initialize(vars)
    super()
    self.strict = false
    @require_empty = false
    @vars = vars
    @tokens = Array.new
  end
  
  def process_iasgn(exp)
    parse_assignment(exp)
  end
  
  def process_lasgn(exp)
    parse_assignment(exp)
  end
  
  def parse_assignment(exp)
    type = exp.shift
    var = exp.shift
    
    if(@vars.include?(var))
      @tokens.concat look_for_tokens(exp)
    end
    
    return s(type, var, exp.shift)
  end
  
  def look_for_tokens(exp) 
    tokens = Array.new
    
    until(exp.empty?)
      type = exp.shift
      
      if(type.is_a? Symbol)
        if(type == :lit || type == :str)
          argvalue = exp.shift
          token = Token.new
          token.importance = 40
          token.type = 'literal'
          token.value = argvalue
          tokens << token
        end
      else
        tokens << look_for_tokens(type)
      end
    end
    return tokens.compact.flatten
  end
end

syntax_tree = RubyParser.new.parse(unit_test_str)
# pp syntax_tree
# exit

tree_processor = AstProcessor.new
tree_processor.process(syntax_tree)

 # pp tree_processor.features

vars = tree_processor.vars

vars.each do |feature, undef_vars|
  syntax_tree = RubyParser.new.parse(unit_test_str)
  token_finder = TokenFinder.new(undef_vars)
  token_finder.process(syntax_tree)
  
  feature.tokens.concat token_finder.tokens
  
end

features = tree_processor.features

db = Database::new
db.save_features(features)
